#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <wchar.h>
#include <dirent.h>
#include <signal.h>
#include <vector>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/socket.h>
#include <netdb.h>
#include "utils.hpp"


#pragma GCC visibility push(hidden)

//Reads the next 'maxLength' number of characters in the fileStream specified
// and returns if the last char read was a number or something else.
//It takes the number chars read (if any) and converts them to an int while
// returning the type of last char read: 0 for a space, 1 for a new line,
// 2 for EOF, -1 if the chars read were all numbers, and -2 if it was anything
// else.
//maxLength's of <=0, invalid FILE streams and an uninitialised val input are
// not handled so unexpected results or errors may occur if invalid.
int read_number(int maxLength, FILE* fileStream, int* val)
{
	int c;
	char build[maxLength + 1];
	int i = 0;
	int errorCode = -1;
	while (i <= maxLength) {
		c = fgetc(fileStream);
		if (c >= '0' && c <= '9') {
			build[i++] = c;
			continue;
		} else if (c == ' ') {
			errorCode = 0;
		} else if (c == '\n') {
			errorCode = 1;
		} else if (c == EOF) {
			errorCode = 2;
		} else {
			errorCode = -2;
		}
		build[i++] = 0;
		break;
	}
	
	if (errorCode == -1) {
		build[i - 1] = 0;
	}
	
	*val = atoi(build);
	
	return errorCode;
}

#pragma GCC visibility pop

bool FloatIsNaN(float &vagueFloat)
{
	uint32_t* vague = (uint32_t*)&vagueFloat;
	if ((*vague >= 0x7F800000 && *vague <= 0x7FFFFFFF) || (*vague >= 0xFF800000 && *vague <= 0xFFFFFFFF)) {
		return true;
	}
	return false;
}

bool rfc3986_allow(char i)
{
	//isalnum(i)//PoS crashes
	if ((i >= '0' && i <= '9')
		|| (i >= 'a' && i <= 'z')
		|| (i >= 'A' && i <= 'Z')
		|| i == '~' || i == '-' || i == '.' || i == '_'
	) {
		return true;
	}
	return false;
}

char* encode_rfc3986(char* label_literal, int label_literal_length)
{
	if (label_literal_length < 0) {
		label_literal_length = strlen(label_literal);
	}
	int escaped_buflen = (label_literal_length * 3) + 1;
	char* label_escaped = new char[escaped_buflen];
	int escaped_buff_i = 0;
	
	for (int i = 0; i < label_literal_length; i++) {
		unsigned char uletter = label_literal[i];
		if (!rfc3986_allow(uletter)) {
			snprintf(label_escaped + escaped_buff_i, 4, "%%%02hhX", uletter);
			escaped_buff_i += 2;
		}
		else {
			label_escaped[escaped_buff_i] = label_literal[i];
		}
		escaped_buff_i++;
	}
	label_escaped[escaped_buff_i] = 0;
	return label_escaped;
}

int TrimRemoveConsecutiveSpaces(char* text)
{
	int text_len = strlen(text);
	int text_pos = 0;
	for (int j = 0; j < text_len; j++) {
		if (text_pos == 0) {
			if (text[j] != ' ') {
				text[text_pos++] = text[j];
			}
			continue;
		}
		if (!(text[j] == ' ' && text[text_pos - 1] == ' ')) {
			text[text_pos++] = text[j];
		}
	}
	text[text_pos] = 0;
	if (text[text_pos - 1] == ' ') {
		text[--text_pos] = 0;
	}
	return text_pos;//new length
}

void PadCStringWithChar(char* strToPad, int toFullLength, char c)
{
	for (int i = strlen(strToPad); i < toFullLength - 1; i++) {
		memset(strToPad + i, c, sizeof(char));
	}
	memset(strToPad + toFullLength - 1, 0, sizeof(char));
}

//if missing versioning parameters, major will always be on leftmost side.
char CmpVersions(char* version_base, char* version_alt)
{
	if (strcmp(version_base, version_alt) == 0) {
		return 0b00000;//same
	}
	
	int versions[2][4];
	for (int j = 0; j < 2; j++) {
		for (int i = 0; i < 4; i++) {
			versions[j][i] = 0;
		}
	}
	
	if (sscanf(version_base, "%d.%d.%d.%d", &versions[0][0], &versions[0][1], &versions[0][2], &versions[0][3]) <= 0) {
		return 0b10000;//invalid
	}
	
	if (sscanf(version_alt, "%d.%d.%d.%d", &versions[1][0], &versions[1][1], &versions[1][2], &versions[1][3]) <= 0) {
		return 0b10000;//invalid
	}
	
	for (int i = 0; i < 4; i++) {
		if (versions[0][i] == versions[1][i]) {
			continue;
		}
		else if (versions[0][i] > versions[1][i]) {//alt is old
			return 0b10000 | (0b1000 >> i);
		}
		else {//alt is new
			return 0b00000 | (0b1000 >> i);
		}
	}
	
	return 0b00000;//same
	//return 0b01000;//new major
	//return 0b00100;//new minor
	//return 0b00010;//new revision
	//return 0b00001;//new build
	//return 0b11000;//old major
	//return 0b10100;//old minor
	//return 0b10010;//old revision
	//return 0b10001;//old build
}

void wcstombs2(char* buffer, wchar_t* text, int buf_len)
{
	int loop_len = wcslen(text);
	if (loop_len > buf_len) {
		loop_len = buf_len;
	}
	int buffer_i = 0;
	for (int i = 0; i < loop_len; i++) {
		if (text[i] >= 0 && text[i] <= 0xFF) {
			buffer[buffer_i++] = (char)text[i];
		}
	}
	buffer[buffer_i] = 0;
}

// Assuming whitespace trimmed pathname.
// Can handle forward or back slash combined paths.
const char* LastPathItem(const char *pathname)
{
	return LastPathItem(pathname, EOF);
}
const char* LastPathItem(const char *pathname, size_t end_length)
{
	if (!pathname) {
		return 0;
	}
	
	const char *result = pathname;
	size_t pathnameLen = end_length != EOF ? end_length : strlen(pathname);
	
	// skip the last normal char and null sentinel
	// (since we don't want to break on a folder path ending with a slash).
	for (const char *pathnameScan = &pathname[pathnameLen - 2]; pathnameScan >= pathname; pathnameScan--) {
		if (*pathnameScan == '/' || *pathnameScan == '\\') {
			result = pathnameScan + 1;
			break;
		}
	}
	
	return result;
}

// Can handle forward or back slash combined paths.
const char* FilenameFromPathname(const char *pathname)
{
	if (!pathname) {
		return 0;
	}
	
	const char *result = pathname;
	size_t pathnameLen = strlen(pathname);
	
	// skip the null sentinel
	for (const char *pathnameScan = &pathname[pathnameLen - 1]; pathnameScan > pathname; pathnameScan--) {
		if (*pathnameScan == '/' || *pathnameScan == '\\') {
			result = pathnameScan + 1;
			break;
		}
	}
	
	return result;
}

// new'ly[] allocates a string with just the path not including the filename.
char* PathFromFilename(const char *filename_path)
{
	const char *filename = FilenameFromPathname(filename_path);
	if (!filename) {
		return 0;
	}
	
	size_t pathBufLen = filename - filename_path + 1;
	char *path = new char[pathBufLen];
	memcpy(path, filename_path, pathBufLen-1);
	path[pathBufLen-1] = 0;
	
	return path;
}

// Find the number of chars that are the same between the primary and secondary string (does not include end sentinel).
// If identical then { result == strlen(primary) }.
size_t StrSameChrFor(const char *primary, const char *secondary)
{
	if (!primary || !secondary) {
		return 0;
	}
	
	for (size_t i = 0; true; i++) {
		if (primary[i] != secondary[i]
			|| primary[i] == 0
			|| primary[i] == EOF
			|| secondary[i] == 0
			|| secondary[i] == EOF
		) {
			return i;
		}
	}
	
	return 0;
}

// Same as StrSameChrFor except it only checks for whole path items (whole matching directories and ending file).
size_t StrSamePathFor(const char *primary, const char *secondary)
{
	if (!primary || !secondary) {
		return 0;
	}
	
	size_t i = 0;
	
	// Search for first difference or end of string.
	while (1) {
		if (primary[i] != secondary[i]
			|| primary[i] == 0
			|| primary[i] == EOF
			|| secondary[i] == 0
			|| secondary[i] == EOF
		) {
			break;
		}
		i++;
	}
	
	if (i != 0 && (primary[i] != 0 || secondary[i] != 0)) {
		// Go back if this is not the same path item.
		while (1) {
			if (primary[i] == '/' || primary[i] == '\\') {
				i++;
				break;
			}
			if (i == 0) {
				break;
			}
			i--;
		}
	}
	
	return i;
}

size_t LenOfFirstPathItem(const char *pathname)
{
	size_t result = 0;
	char chr;
	while (1) {
		chr = pathname[result];
		if (chr == 0) {
			break;
		}
		result++;
		if (chr == '/' || chr == '\\') {
			break;
		}
	}
	return result;
}

// new'ly[] allocates a string for the end item found before the end_position.
char* GetItemBeforeLastPos(const char *full_pathname, const char *&end_position)
{
	if (full_pathname == 0 || (end_position != 0 && end_position <= full_pathname)) {
		return 0;
	}
	if (end_position == 0) {
		end_position = full_pathname + strlen(full_pathname);
	}
	
	const char *start_position = LastPathItem(full_pathname, end_position - full_pathname);
	
	size_t resultBufLen = end_position - start_position + 1;
	char *result = new char[resultBufLen];
	strncpy(result, start_position, resultBufLen);
	result[resultBufLen-1] = 0;
	
	end_position = start_position;
	
	return result;
}

// Reads the path and returns the path formatted without relative directory notation.
char* GetRealPath(const char *path)
{
	char *realPath = realpath(path, NULL);
	if (!realPath) {
		return 0;
	}
	
	size_t realPathLen = strlen(realPath);
	
	DIR *dir = opendir(realPath);
	char* result;
	if (!dir) {
		result = new char[realPathLen+1];
		memcpy(result, realPath, realPathLen+1);
		free(realPath);
		return result;
	}
	closedir(dir);
	
	// If it is a folder then add the '/'.
	result = new char[realPathLen + 2];
	memcpy(result, realPath, realPathLen);
	free(realPath);
	result[realPathLen] = '/';
	result[realPathLen+1] = 0;
	
	return result;
}

bool EndsWith(const char *str, const char *suffix)
{
	if (!str || !suffix) {
		return false;
	}
	size_t lenstr = strlen(str);
	size_t lensuffix = strlen(suffix);
	if (lensuffix > lenstr) {
		return false;
	}
	return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
}

// new'ly[] allocate a copy of this input string.
char* CloneString(const char *str)
{
	size_t strBufLen = strlen(str) + 1;
	char *result = new char[strBufLen];
	memcpy(result, str, strBufLen);
	return result;
}

char* FormMallocString(const char *const format, ...)
{
	size_t currentLen = 1;
	size_t currentBufLen = sizeof(char) * currentLen;
	char *resultStr = (char*)malloc(currentBufLen);

	va_list args;
	while (1) {
		va_start(args, format);
		int needLen = vsnprintf(resultStr, currentLen, format, args);
		va_end(args);
		if (needLen < 0) {
			// string formatting error.
			free(resultStr);
			return 0;
		}
		if ((size_t)needLen >= currentLen) {
			currentLen = needLen + 1;
			currentBufLen = sizeof(char) * currentLen;
			resultStr = (char*)realloc(resultStr, currentBufLen);
		}
		else {
			break;
		}
	}
	return resultStr;
}

// new'ly[] allocates a 32 byte buffer to store the given string format.
uint8_t* Sha256StrToByteArray(const char *checksumStr)
{
	if (!checksumStr || strlen(checksumStr) != 64) {
		return 0;
	}
	
	char *checksumStrCpy = CloneString(checksumStr);
	uint8_t *checksum = new uint8_t[32];
	for (int8_t i = 32 - 1; i >= 0; i--) {
		sscanf(&checksumStrCpy[i*2], "%hhx", &checksum[i]);
		checksumStrCpy[i*2] = 0;
	}
	delete[] checksumStrCpy;
	return checksum;
}

// new'ly[] allocates a 65 char buffer to store the given byte format.
char* Sha256ByteArrayToStr(const uint8_t *checksum)
{
	if (!checksum) {
		return 0;
	}
	
	char *checksumStr = new char[65];
	for (uint8_t i = 0; i < 32; i++) {
		sprintf(&checksumStr[i*2], "%02hhx", checksum[i]);
	}
	checksumStr[64] = 0;
	
	return checksumStr;
}

uint16_t GetSockAddrPort(const sockaddr_storage *sockAddrStorage)
{
	uint16_t portHBO =
		sockAddrStorage->ss_family == AF_INET
		? ntohs((*(sockaddr_in*)sockAddrStorage).sin_port)
		: (
			sockAddrStorage->ss_family == AF_INET6
			? ntohs((*(sockaddr_in6*)sockAddrStorage).sin6_port)
			: 0
			);
	return portHBO;
}

bool SetSockAddrPort(sockaddr_storage *sockAddrStorage, uint16_t portHBO)
{
	switch (sockAddrStorage->ss_family) {
	case AF_INET:
		(*(sockaddr_in*)sockAddrStorage).sin_port = htons(portHBO);
		break;
	case AF_INET6:
		(*(sockaddr_in6*)sockAddrStorage).sin6_port = htons(portHBO);
		break;
	default:
		return false;
	}
	return true;
}

/// Malloc'd result.
char* GetSockAddrInfo(const sockaddr_storage *sockAddrStorage)
{
	// Maximum length of full domain name + sentinel.
	char namebuf[253 + 1];
	int error = getnameinfo((sockaddr*)sockAddrStorage, sizeof(sockaddr_storage), namebuf, sizeof(namebuf), NULL, 0, NI_NUMERICHOST);
	if (!error) {
		uint16_t portHBO = GetSockAddrPort(sockAddrStorage);
		return portHBO
			? (
				sockAddrStorage->ss_family == AF_INET
				? FormMallocString("%s:%hu", namebuf, portHBO)
				: FormMallocString("[%s]:%hu", namebuf, portHBO)
			)
			: FormMallocString("%s", namebuf);
	}
	return 0;
}

bool SockAddrsMatch(const sockaddr_storage *sockAddr1, const sockaddr_storage *sockAddr2)
{
	if (sockAddr1->ss_family != sockAddr2->ss_family) {
		return false;
	}

	switch (sockAddr1->ss_family) {
	case AF_INET:
		if (
			((sockaddr_in*)sockAddr1)->sin_port != ((sockaddr_in*)sockAddr2)->sin_port
			|| ((sockaddr_in*)sockAddr1)->sin_addr.s_addr != ((sockaddr_in*)sockAddr2)->sin_addr.s_addr
			) {
			return false;
		}
		break;
	case AF_INET6:
		if (
			((sockaddr_in6*)sockAddr1)->sin6_port != ((sockaddr_in6*)sockAddr2)->sin6_port
			|| ((sockaddr_in6*)sockAddr1)->sin6_flowinfo != ((sockaddr_in6*)sockAddr2)->sin6_flowinfo
			|| ((sockaddr_in6*)sockAddr1)->sin6_scope_id != ((sockaddr_in6*)sockAddr2)->sin6_scope_id
			) {
			return false;
		}
		if (memcmp(((sockaddr_in6*)sockAddr1)->sin6_addr.s6_addr, ((sockaddr_in6*)sockAddr2)->sin6_addr.s6_addr, sizeof(in6_addr)) != 0) {
			return false;
		}
		break;
	default:
		if (memcmp(sockAddr1, sockAddr2, sizeof(sockaddr_storage)) != 0) {
			return false;
		}
		break;
	}

	return true;
}
